package hz;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

/**
 * Game Protocols.
 * 
 * @author Hechuan
 * 
 */
public class GameProtocol {

	private static final int gridsize = 10;

	private static final int win = 5;

	private static final ObjectMapper mapper = new ObjectMapper();
	private static String dummyErrMsg = "{\"status\":\"oops\"}";
	private static String dummyOkMsg = "{\"status\":\"ok\"}";

	private static final String ACTION_NEW_USER = "newuser";
	private static final String ACTION_CREAT_ROOM = "createroom";
	private static final String ACTION_LS_ROOM = "lsroom";
	private static final String ACTION_SIGN_IN = "signin";
	private static final String ACTION_JOIN = "join";
	private static final String ACTION_GET_USER = "getuser";
	private static final String ACTION_MOVE = "move";
	private static final String ACTION_ABANDON = "abandon";
	private static final String ACTION_LEADER_BOARD = "ranking";

	private GameThread t;
	private String myuserid;

	public GameProtocol(GameThread t) {
		this.t = t;
	}

	/**
	 * This is our dispatcher.
	 * 
	 * @param in
	 * @return
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public String processInput(String in) throws IOException {
		if (in == null)
			return "Hello";
		if ("bye".equals(in))
			return "bye";
		try {
			Map<String, Object> map = mapper.readValue(in, Map.class);
			String action = (String) map.get("action");
			if (action.equals(ACTION_NEW_USER))
				return createUser(map);
			if (action.equals(ACTION_SIGN_IN))
				return signIn(map);
			if (action.equals(ACTION_CREAT_ROOM))
				return createRoom(map);
			if (action.equals(ACTION_LS_ROOM))
				return listRooms(map);
			if (action.equals(ACTION_JOIN))
				return joinRoom(map);
			if (action.equals(ACTION_GET_USER))
				return findUser(map);
			if (action.equals(ACTION_MOVE))
				return move(map);
			if (action.equals(ACTION_ABANDON))
				return abandon(map);
			if (action.equals(ACTION_LEADER_BOARD))
				return ranking(map);
		} catch (JsonParseException | JsonMappingException e) {

		}

		return dummyErrMsg;
	}

	/**
	 * {"action":"newuser", "userid":"woailinger", "passwd":"123123123"}
	 * 
	 * @param map
	 * @return
	 * @throws IOException
	 * @throws JsonMappingException
	 * @throws JsonGenerationException
	 */
	public String createUser(Map<String, Object> map)
			throws JsonGenerationException, JsonMappingException, IOException {
		String userid = (String) map.get("userid");
		String passwd = (String) map.get("passwd");

		if (userid == null || passwd == null)
			return dummyErrMsg;

		userid = userid.trim();
		passwd = passwd.trim();

		if (userid.length() < 6 || passwd.length() < 6)
			return dummyErrMsg;
		long id = UserService.createUser(userid, passwd);

		if (id < 1)
			return dummyErrMsg;
		t.addToGlabalList((int) id); // wtf!
		myuserid = userid;
		map.put("id", id);

		return mapper.writeValueAsString(map);
	}

	/**
	 * {"action":"signin", "userid":"woailinger", "passwd":"123123123"}
	 * 
	 * @param map
	 * @return
	 * @throws IOException
	 * @throws JsonMappingException
	 * @throws JsonGenerationException
	 */
	public String signIn(Map<String, Object> map)
			throws JsonGenerationException, JsonMappingException, IOException {
		String userid = (String) map.get("userid");
		String passwd = (String) map.get("passwd");

		if (userid == null || passwd == null)
			return dummyErrMsg;

		User u = UserService.refreshUser(userid, passwd);
		if (u.getId() <= 0)
			return dummyErrMsg;
		t.addToGlabalList((int) u.getId());
		myuserid = u.getUserid();
		return mapper.writeValueAsString(u);

	}

	/**
	 * {"action":"createroom","id":1,"roomname":"i am good",
	 * "passwdforroom":"123"}
	 */

	private static final Map<Room, String> free_rooms = new LinkedHashMap<>();
	private static final Map<Integer, Board> ongoing_rooms = new LinkedHashMap<>();

	public String createRoom(Map<String, Object> map)
			throws JsonGenerationException, JsonMappingException, IOException {
		Integer aid = (Integer) map.get("id");
		String roomname = (String) map.get("roomname");
		String passwdforroom = (String) map.get("passwdforroom");

		if (aid == null || roomname == null)
			return dummyErrMsg;

		Room room = new Room();
		int ret = RoomService.createRoom(aid);

		room.setId(ret);
		room.setAid(t.id);
		room.setName(roomname);
		room.setNeedPassword(StringUtils.isNoneBlank(passwdforroom));
		room.setAuserid(myuserid);

		free_rooms.put(room,
				StringUtils.isNoneBlank(passwdforroom) ? passwdforroom : null);

		map.put("room", room);
		return mapper.writeValueAsString(map);
	}

	/**
	 * {"action":"lsroom"}
	 * 
	 * @return
	 * @throws IOException
	 * @throws JsonMappingException
	 * @throws JsonGenerationException
	 */
	private String listRooms(Map<String, Object> map)
			throws JsonGenerationException, JsonMappingException, IOException {
		List<Room> list = new ArrayList<>();

		for (Room room : free_rooms.keySet())
			list.add(room);

		map.put("list", list);

		return mapper.writeValueAsString(map);
	}

	/**
	 * {"action":"ranking"}
	 * 
	 * @return
	 * @throws IOException
	 * @throws JsonMappingException
	 * @throws JsonGenerationException
	 */
	private String ranking(Map<String, Object> map)
			throws JsonGenerationException, JsonMappingException, IOException {

		User[] list = UserService.globalRanking();

		map.put("players", list);
		
		int myrank = UserService.ranking(t.id);
		map.put("myranking", myrank);
		int myscore = UserService.popluateUser(t.id).getScore();
		map.put("myscore", myscore);

		return mapper.writeValueAsString(map);
	}

	private int rivalId = -1;

	/**
	 * {"action":"join", "bid":3, "roomid":14, "buserid":"xxx"}
	 * 
	 * @param map
	 * @return
	 */
	private String joinRoom(Map<String, Object> map) {
		int roomid = (int) map.get("roomid");

		for (Room room : free_rooms.keySet()) {
			if (room.getId() == roomid) { // match
				if (free_rooms.get(room) != null) { // need passwd
					String passwd = (String) map.get("passwd");
					System.out.println(passwd + " " + free_rooms.get(room));
					if (free_rooms.get(room).equals(passwd)) {
						// cannot be joined any more
						RoomService.joinRoom(t.id, roomid);
						free_rooms.remove(room);
						rivalId = room.getAid();
						ongoing_rooms.put(roomid, new Board(rivalId, t.id));
						return t.tellAMyId(rivalId) ? dummyOkMsg : dummyErrMsg;
					}
					return dummyErrMsg;
				}
				// no passwd needed
				rivalId = room.getAid();
				RoomService.joinRoom(t.id, roomid);
				free_rooms.remove(room);
				ongoing_rooms.put(roomid, new Board(rivalId, t.id));
				return t.tellAMyId(rivalId) ? dummyOkMsg : dummyErrMsg;

			}
		}

		return dummyErrMsg;
	}

	private String abandon(Map<String, Object> map) {
		int to = (int) map.get("to");
		int roomid = (int) map.get("roomid");
		t.tellWhoWhereIMove(to, -1, -1, "abandoned");
		RoomService.leaveRoom(roomid, (byte) -1);
		ongoing_rooms.remove(roomid);
		return dummyOkMsg;
	}

	private String move(Map<String, Object> map) {
		int row = (int) map.get("row");
		int col = (int) map.get("col");
		int to = (int) map.get("to");
		int roomid = (int) map.get("roomid");

		Board b = ongoing_rooms.get(roomid);
		if (b == null)
			return dummyErrMsg;

		int judge = b.move(t.id, row, col);

		if (judge == 0 || judge == 1) {
			t.tellWhoWhereIMove(to, row, col, "youlose");
			RoomService.leaveRoom(roomid, (byte) judge);
			ongoing_rooms.remove(roomid);
			return "{\"status\":\"youwin\"}";
		}

		if (judge == 2) {
			RoomService.leaveRoom(roomid, (byte) judge);
			t.tellWhoWhereIMove(to, row, col, "draw");
			ongoing_rooms.remove(roomid);
			return "{\"status\":\"draw\"}";
		}

		if (judge == -1)
			return dummyErrMsg;

		return t.tellWhoWhereIMove(to, row, col, null) ? dummyOkMsg
				: dummyErrMsg;
	}

	private String findUser(Map<String, Object> map)
			throws JsonGenerationException, JsonMappingException, IOException {
		int id = (int) map.get("id");

		User u = UserService.popluateUser(id);
		if (u.getId() <= 0)
			return dummyErrMsg;
		map.put("user", u);

		return mapper.writeValueAsString(map);
	}

	private class Board {
		int aid, bid, amoves, bmoves;
		char[][] grids = new char[gridsize][gridsize];

		String awinstr;
		String bwinstr;

		public Board(int aid, int bid) {
			this.aid = aid;
			this.bid = bid;
			amoves = 0;
			bmoves = 0;

			StringBuilder sba = new StringBuilder();
			StringBuilder sbb = new StringBuilder();

			for (int i = 0; i != win; ++i) {
				sba.append('a');
				sbb.append('b');
			}

			awinstr = sba.toString();
			bwinstr = sbb.toString();
		}

		/**
		 * 1: awin, 0: bwin, 2: draw, 3: unsolved, -1: invalid
		 * 
		 * @return
		 */
		int move(int id, int row, int col) {
			if (row >= gridsize || col >= gridsize || grids[row][col] != 0)
				return -1;

			int ret = -1;
			if (id == aid && amoves == bmoves) {
				++amoves;
				grids[row][col] = 'a';
				ret = judge();
			} else if (id == bid && amoves == bmoves + 1) {
				++bmoves;
				grids[row][col] = 'b';
				ret = judge();
			}

			if (ret == 0)
				UserService.updateUserScore((int) bid, 3);
			else if (ret == 1)
				UserService.updateUserScore((int) aid, 3);
			else if (ret == 2) {
				UserService.updateUserScore((int) aid, 1);
				UserService.updateUserScore((int) bid, 1);
			}

			return ret;
		}

		/**
		 * 0: awin, 1: bwin, 2: draw, 3: unsolved
		 * 
		 * @return
		 */
		private int judge() {

			// process rows
			for (int i = 0; i != gridsize; ++i) {
				StringBuilder sb = new StringBuilder();
				for (int j = 0; j != gridsize; ++j)
					sb.append(grids[i][j]);

				String row = sb.toString();
				if (row.indexOf(awinstr) >= 0)
					return 1;
				if (row.indexOf(bwinstr) >= 0)
					return 0;
			}

			// process columns
			for (int i = 0; i != gridsize; ++i) {
				StringBuilder sb = new StringBuilder();
				for (int j = 0; j != gridsize; ++j)
					sb.append(grids[j][i]);

				String col = sb.toString();

				if (col.indexOf(awinstr) >= 0)
					return 1;
				if (col.indexOf(bwinstr) >= 0)
					return 0;
			}

			// diagonal
			for (int i = gridsize - win; i >= 0; --i) {
				StringBuilder sb = new StringBuilder();
				for (int j = 0; j != gridsize - i; ++j)
					sb.append(grids[i + j][j]);

				String diagonal = sb.toString();

				if (diagonal.indexOf(awinstr) >= 0)
					return 1;
				if (diagonal.indexOf(bwinstr) >= 0)
					return 0;
			}

			for (int i = gridsize - win; i > 0; --i) {
				StringBuilder sb = new StringBuilder();
				for (int j = 0; j != gridsize - i; ++j)
					sb.append(grids[j][i + j]);

				String diagonal = sb.toString();
				if (diagonal.indexOf(awinstr) >= 0)
					return 1;
				if (diagonal.indexOf(bwinstr) >= 0)
					return 0;
			}

			for (int i = win - 1; i < gridsize; ++i) {
				StringBuilder sb = new StringBuilder();
				for (int j = 0; j != i + 1; ++j)
					sb.append(grids[i - j][j]);

				String diagonal = sb.toString();
				if (diagonal.indexOf(awinstr) >= 0)
					return 1;
				if (diagonal.indexOf(bwinstr) >= 0)
					return 0;
			}

			for (int i = gridsize - win; i > 0; --i) {
				StringBuilder sb = new StringBuilder();
				for (int j = 0; j != gridsize - i; ++j)
					sb.append(grids[gridsize - 1 - j][i + j]);

				String diagonal = sb.toString();
				if (diagonal.indexOf(awinstr) >= 0)
					return 1;
				if (diagonal.indexOf(bwinstr) >= 0)
					return 0;
			}

			if (amoves + bmoves == gridsize * gridsize)
				return 2;

			return 3;
		}

	}
}
